/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/**
 * 1、Promise 就是一个类，在执行这个类的时候，需要传递一个执行器（函数）进去，执行器会立即执行
 * 2、Promise 中有三种状态，分别为成功（fulfilled），失败（rejected），等待（pending）
 *      状态只有两种变化：
 *      pending --> fulfilled
 *      pending --> rejected
 *      一旦状态确定就不可更改
 * 3、执行器有两个参数，分别是 resolve 和 reject，这两个函数是用来更改状态的
 *      resolve：将状态变为成功（fulfilled），可传递成功后的值
 *      reject：将状态变为失败（rejected），可传递失败后的原因
 * 4、then 方法接受两个回调函数，分别为成功的回调和失败的回调，
 *   方法内部做的事情就是判断状态，根据状态的不同去调用成功或者失败的回调函数，then 方法被定义在原型对象中
 * 5、then 成功回调有一个参数，表示成功之后的值；失败回调有一个参数，表示失败后的原因
 * */

// 状态使用常量保存，一方面为了代码提示，另一方面方便复用
const PENDING = "pending"; // 等待
const FULFILLED = "fulfilled"; // 成功
const REJECTED = "rejected"; // 失败

class MyPromise {
    // 传入执行器 executor
    constructor(executor) {
        // 执行器立即执行，并且有两个参数 resolve 和 reject
        // 同时还要捕获执行器是否会运行错误
        try {
            executor(this.resolve, this.reject);
        } catch (e) {
            this.reject(e)
        }
    }

    // 定义Promise状态，默认值是等待
    status = PENDING;

    // 成功后的值
    value = undefined;
    // 失败后的原因
    reason = undefined;

    // 默认值为数组，这样才可以保存多个回调函数
    // 成功回调
    successCallback = [];
    // 失败回调
    failCallback = [];

    // 使用箭头函数，确保this指向
    // value 参数表示成功后的值
    resolve = (value) => {
        // 如果状态不是等待，阻止程序向下执行
        if (this.status !== PENDING) return;
        // 更改状态为成功
        this.status = FULFILLED;
        // 保存成功之后的值
        this.value = value;
        // 若成功回调存在，则调用
        while (this.successCallback.length) {
            this.successCallback.shift()();
        }
    }

    // reason 参数 表示失败后的原因
    reject = (reason) => {
        // 如果状态不是等待，阻止程序向下执行
        if (this.status !== PENDING) return;
        // 更改状态为失败
        this.status = REJECTED;
        // 保存失败后的原因
        this.reason = reason;
        // 若失败回调存在，则调用
        while (this.failCallback.length) {
            // 使用 shift 不仅可以取出数组中的回调函数进行执行，而且可以将其从保存的数组中剔除
            this.failCallback.shift()();
        }
    }

    // 为了实现then方法的链式调用，then 方法应该返回一个 Promise
    then(successCallback, failCallback) {
        // then 方法的参数是可选参数，如无参数，设置默认值
        successCallback = successCallback ? successCallback : value => value;
        failCallback = failCallback ? failCallback : reason => {
            throw reason
        };

        let promise2 = new MyPromise((resolve, reject) => {
            // 判断状态，执行回调
            if (this.status === FULFILLED) {
                // 成功的回调传递成功的值
                // 使用异步为了保证同步代码运行完成，此时才可以在内部使用 promise2
                // 此处代码 个人由 setTimeout 切换成了 创建微任务 queueMicrotask
                queueMicrotask(() => {
                    try {
                        /**
                         * 声明变量 x 接收成功回调的返回值
                         * 判断 x 是普通值还是 promise 对象
                         * 如果是普通值 直接调用 resolve
                         * 如果是 promise ，则需要查看 promise 对象返回的结果
                         * 根据结果调用 resolve 或 reject
                         * 这段逻辑在三种状态中都需要运行，因此抽离为函数
                         * */
                        let x = successCallback(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            } else if (this.status === REJECTED) {
                // 失败的回调传递失败的原因
                queueMicrotask(() => {
                    try {
                        // 基本原理同上
                        let x = failCallback(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            } else {
                // 等待状态(处理异步逻辑)
                // 将成功和失败的回调存储起来，等待之后的状态执行
                this.successCallback.push(() => {
                    queueMicrotask(() => {
                        try {
                            let x = successCallback(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                this.failCallback.push(() => {
                    queueMicrotask(() => {
                        try {
                            let x = failCallback(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        });

        return promise2;
    }

    /**
     * all 方法，用于将多个 Promise 实例，包装成一个新的 Promise 实例
     * 接受一个数组作为参数
     * */
    static all(array) {
        // 返回结果
        let result = [];
        // 计数器--用来判断当前数组中的回调函数是否执行完成
        let index = 0;

        return new MyPromise((resolve, reject) => {
            function addData(key, value) {
                result[key] = value;
                // 每添加成功一个值，说明函数执行完毕一个，计数器自增
                index++;
                // 当计数器 === 数组长度，表明各个实例执行完毕，执行成功回调
                if (index === array.length) {
                    resolve(result)
                }
            }

            for (let i = 0; i < array.length; i++) {
                let current = array[i];
                // 判断 current 的类型
                if (current instanceof MyPromise) {
                    // promise 对象，调用then方法
                    current.then((value) => {
                        addData(i, value);
                    }, (reason) => {
                        // 有一个实例的状态是失败，all的状态就为失败
                        reject(reason);
                    })
                } else {
                    // 普通值
                    addData(i, current);
                }
            }
        });
    }

    // resolve 方法
    static resolve(value) {
        if (value instanceof MyPromise) {
            // promise 对象，直接返回这个对象
            return value;
        } else {
            // 普通值，返回一个新的 Promise 对象，状态为resolved
            return new MyPromise(resolve => {
                resolve(value);
            })
        }
    }

    /**
     * finally 方法用于指定不管 Promise 对象最后状态如何，都会执行的操作
     * 并且可以链式调用
     * finally方法总是会返回原来的值
     * */
    finally(callback) {
        // 通过 then 方法获取状态
        return this.then((value) => {
            return MyPromise.resolve(callback()).then(() => value);
        }, (reason) => {
            return MyPromise.resolve(callback()).then(() => {
                throw reason;
            });
        })
    }

    /**
     * catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名，用于指定发生错误时的回调函数
     * */
    catch(failCallback) {
        return this.then(null, failCallback);
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    // 解决链式调用返回自己的问题
    if (x === promise2) {
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"))
    }
    // x 是一个 promise对象
    if (x instanceof MyPromise) {
        // 调用then方法，更改状态
        x.then(resolve, reject);
    } else {
        // 普通值，直接返回
        resolve(x);
    }
}

module.exports = MyPromise;
